﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Acl.DataCheck
{
    /// <summary>
    /// 外键约束校验器
    /// </summary>
    internal class FKValidator : ConstraintValidator
    {
        private static readonly Dictionary<int, Tuple<string, FK>> _caches = [];

        internal class FK
        {
            public string TableName { get; set; }
            public string ColumnName { get; set; }

            /// <summary>
            /// 当列存在时才可以进行外键检查
            /// </summary>
            public bool Existed { get; set; }

            public List<Condition> Conditions { get; } = [];
        }

        public static FK Parse(ConstraintItem constraint, StringBuilder log)
        {
            var expression = constraint.Expression;

            var key = HashCode.Combine(expression);

            if (!_caches.TryGetValue(key, out var tuple))
            {
                _caches[key] = tuple = ParseFK(constraint);
            }

            if (!string.IsNullOrEmpty(tuple.Item1))
            {
                log.AppendLine(tuple.Item1);
                return default;
            }

            return tuple.Item2;
        }

        internal class Condition
        {
            public string Left { get; set; }
            public string Right { get; set; }
            public string Operation { get; set; }
        }

        private static Tuple<string, FK> ParseFK(ConstraintItem constraint)
        {
            var expression = constraint.Expression;
            var tokens = ParseToken(expression);
            var length = tokens.Length;

            if (length is not (5 or 6 or 9 or 10 or 11 or 12))
                return new Tuple<string, FK>($"FK 表达式格式错误:‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);

            var fk = new FK();
            var i = 0;
            var token = tokens[i++];

            fk.Existed = token.Length == 7 &&
                  token[0] is 'e' or 'E' &&
                  token[1] is 'x' or 'X' &&
                  token[2] is 'i' or 'I' &&
                  token[3] is 's' or 'S' &&
                  token[4] is 't' or 'T' &&
                  token[5] is 'e' or 'E' &&
                  token[6] is 'd' or 'D';
            if (fk.Existed)
            {
                token = tokens[i++];
            }

            if (token.Length != 3 || !
                 (token[0] is 'r' or 'R' &&
                    token[1] is 'e' or 'E' &&
                    token[2] is 'f' or 'F'))
            {
                return new Tuple<string, FK>($"FK 表达式格式错误,表达式必须要‘ref’ 或者 'existed ref' 开头: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
            }

            fk.TableName = tokens[i++];

            token = tokens[i++];
            if (token.Length != 1 && token[0] != '(' && token[0] != '（')
            {
                return new Tuple<string, FK>($"FK 表达式格式错误,缺失 ‘(’ 符号: {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
            }

            fk.ColumnName = tokens[i++];

            token = tokens[i++];
            if (token.Length != 1 && token[0] != ')' && token[0] != '）')
            {
                return new Tuple<string, FK>($"FK 表达式格式错误,缺失 ‘(’ 符号: {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
            }

            if (length is 9 or 10 or 11 or 12) //where 条件
            {
                token = tokens[i++];

                if (token.Length != 5 || !
                (token[0] is 'w' or 'W' &&
                   token[1] is 'h' or 'H' &&
                   token[2] is 'e' or 'E' &&
                   token[3] is 'r' or 'R' &&
                   token[4] is 'e' or 'E'))
                {
                    return new Tuple<string, FK>($"FK 表达式格式错误: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                }

                token = tokens[i++];
                var condition = new Condition();
                fk.Conditions.Add(condition);

                if (length is 9 or 10)
                {
                    if (token.Length == 7 &&
                    (token[0] is 'h' or 'H' &&
                       token[1] is 'a' or 'A' &&
                       token[2] is 's' or 'S' &&
                       token[3] is 'f' or 'F' &&
                       token[4] is 'l' or 'L' &&
                       token[5] is 'a' or 'A' &&
                       token[6] is 'g' or 'G'))
                    {
                        condition.Left = fk.ColumnName;
                        condition.Operation = "HasFlag";

                        //return new Tuple<string, FK>($"FK 表达式格式错误: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                        token = tokens[i++];

                        if (
                           (token.Length == 1 && token[0] is '(' or '（') &&
                           (tokens[length - 1].Length == 1 && tokens[length - 1][0] is ')' or '）'))
                        {
                            condition.Right = tokens[i];
                        }
                        else
                        {
                            return new Tuple<string, FK>($"FK 表达式的where条件格式错误, : {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                        }
                    }
                    else
                    {
                        condition.Left = token;

                        token = tokens[i++];
                        if (token is "=" or "<" or "<=" or ">" or ">=")
                        {
                            condition.Operation = token;
                            condition.Right = tokens[i];
                        }
                        else
                        {
                            return new Tuple<string, FK>($"FK 表达式格式错误: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                        }
                    }
                }
                else
                {
                    condition.Left = token;
                    if (!char.IsLetter(token[0]) && token[0] != '_')
                    {
                        return new Tuple<string, FK>($"FK 表达式的where条件格式错误, : {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                    }

                    token = tokens[i++];
                    if (token.Length != 7 || !
                    (token[0] is 'h' or 'H' &&
                       token[1] is 'a' or 'A' &&
                       token[2] is 's' or 'S' &&
                       token[3] is 'f' or 'F' &&
                       token[4] is 'l' or 'L' &&
                       token[5] is 'a' or 'A' &&
                       token[6] is 'g' or 'G'))
                    {
                        return new Tuple<string, FK>($"FK 表达式格式错误: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                    }

                    condition.Operation = "HasFlag";
                    token = tokens[i++];

                    if (
                       (token.Length == 1 && token[0] is '(' or '（') &&
                       (tokens[length - 1].Length == 1 && tokens[length - 1][0] is ')' or '）'))
                    {
                        condition.Right = tokens[i];
                    }
                    else
                    {
                        return new Tuple<string, FK>($"FK 表达式的where条件格式错误, : {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                    }
                }
            }

            return new Tuple<string, FK>(null, fk);
        }

        public override void Validate(ValidationContext ctx)
        {
            Guard.NotNull(ctx, nameof(ctx));

            if (ctx.Current.Type != ConstraintType.FK)
                throw new NotSupportedException($"{GetType().Name} 不支持 '{ctx.Current.Type}' ,出错相关信息:{ctx.Current} ");

            if (!ctx.Current.Enabled) return;

            if (!ctx.Tables.Contains(ctx.Current.TableName))
                throw new NotSupportedException($"{ctx.Current.TableName} 表名不存在 ,出错相关信息:{ctx.Current} ");

            var table = ctx.Tables[ctx.Current.TableName];
            if (!table.Members.Contains(ctx.Current.ColumnName))
                throw new NotSupportedException($"{ctx.Current.TableName}.{ctx.Current.ColumnName} 列不存在 ,出错相关信息:{ctx.Current} ");

            var columnType = table.Members[ctx.Current.ColumnName].Type;
            var defaultValue = ObjectCreator.GetDefaultValue(columnType);

            //解析表达式
            ctx.Current.ClearErrorMessage();
            var fk = Parse(ctx.Current, ctx.Logger);
            ctx.Current.Passed = fk != null;
            if (!ctx.Current.Passed) return;

            if (!ctx.Tables.Contains(fk.TableName))
                throw new NotSupportedException($"{fk.TableName} 表名不存在 ,出错相关信息:{ctx.Current} ");

            var fkTable = ctx.Tables[fk.TableName];
            if (!fkTable.Members.Contains(fk.ColumnName))
                throw new NotSupportedException($"{fk.TableName}.{fk.ColumnName} 列不存在 ,出错相关信息:{ctx.Current} ");

            var range = ctx.GetFKCodes(fk.TableName, fk.ColumnName);
            if (range.Count != 0 && range[0].GetType() != columnType)
            {
                range = range.Select(x => Converter.Convert(x, columnType)).ToList();
            }

            foreach (dynamic row in table)
            {
                var columnValue = row[ctx.Current.ColumnName];

                if (columnValue is null || Equals(columnValue, defaultValue))
                {
                    if (fk.Existed) continue;
                    else
                    {
                        if (!range.Contains(columnValue))
                        {
                            //
                            ctx.Current.AppendLine($"{ctx.Current.TableName}.{ctx.Current.ColumnName} FK值不能为null 或缺省值，记录信息:{row}");
                            ctx.Current.Passed = false;
                        }
                    }
                }
                else if (!range.Contains(columnValue))
                {
                    //
                    ctx.Current.AppendLine($"{ctx.Current.TableName}.{ctx.Current.ColumnName}={columnValue} FK值在{fk.TableName}.{fk.ColumnName} 中不存在，记录信息:{row}");
                    ctx.Current.Passed = false;
                }
                else
                {
                    foreach (var condition in fk.Conditions)
                    {
                        if (!fkTable.Members.Contains(condition.Left))
                        {
                            throw new NotSupportedException($"{fk.TableName}.{condition.Left} 列不存在 ,出错相关信息:{ctx.Current} ");
                        }

                        var leftType = fkTable.Members[condition.Left].Type;

                        dynamic fkRow = default;
                        foreach (var item in fkTable)
                        {
                            if (item[fk.ColumnName] == columnValue)
                            {
                                fkRow = item;
                                break;
                            }
                        }

                        if (condition.Operation == "HasFlag")
                        {
                            var leftValue = fkRow.Field<long>(condition.Left);
                            var flagValue = long.Parse(condition.Right);

                            if ((leftValue & flagValue) != flagValue)
                            {
                                ctx.Current.AppendLine($"{fk.TableName}.{condition.Left}({leftValue} & {flagValue}) !=  {flagValue}，记录信息:{row}");
                                ctx.Current.Passed = false;
                            }
                        }
                        else
                        {
                            var leftValue = fkRow[condition.Left];

                            dynamic rightValue = Converter.Convert(condition.Right, leftType);

                            var v = leftValue - rightValue;

                            switch (condition.Operation)
                            {
                                case "=":
                                case "==":
                                    if (v != 0)
                                    {
                                        ctx.Current.AppendLine($"{fk.TableName}.{condition.Left} !=  {rightValue}，记录信息:{row}");
                                        ctx.Current.Passed = false;
                                    }
                                    break;

                                case "<":
                                    if (v >= 0)
                                    {
                                        ctx.Current.AppendLine($"{fk.TableName}.{condition.Left} >=  {rightValue}，记录信息:{row}");
                                        ctx.Current.Passed = false;
                                    }
                                    break;

                                case "<=":
                                    if (v > 0)
                                    {
                                        ctx.Current.AppendLine($"{fk.TableName}.{condition.Left} >  {rightValue}，记录信息:{row}");
                                        ctx.Current.Passed = false;
                                    }
                                    break;

                                case ">":
                                    if (v <= 0)
                                    {
                                        ctx.Current.AppendLine($"{fk.TableName}.{condition.Left} <=  {rightValue}，记录信息:{row}");
                                        ctx.Current.Passed = false;
                                    }
                                    break;

                                case ">=":
                                    if (v < 0)
                                    {
                                        ctx.Current.AppendLine($"{fk.TableName}.{condition.Left} <  {rightValue}，记录信息:{row}");
                                        ctx.Current.Passed = false;
                                    }
                                    break;
                            }
                        }
                    }
                }
            }
        }
    }
}